package net.minecraft.server.dedicated;

import com.google.common.collect.Lists;
import com.mojang.authlib.GameProfileRepository;
import com.mojang.authlib.minecraft.MinecraftSessionService;
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Proxy;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import net.minecraft.command.ICommandSender;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.ICrashReportDetail;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.network.rcon.IServer;
import net.minecraft.network.rcon.RConConsoleSource;
import net.minecraft.network.rcon.RConThreadMain;
import net.minecraft.network.rcon.RConThreadQuery;
import net.minecraft.profiler.Snooper;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ServerEula;
import net.minecraft.server.gui.MinecraftServerGui;
import net.minecraft.server.management.PlayerProfileCache;
import net.minecraft.server.management.PreYggdrasilConverter;
import net.minecraft.tileentity.TileEntitySkull;
import net.minecraft.util.CryptManager;
import net.minecraft.util.NonNullList;
import net.minecraft.util.datafix.DataFixer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.GameType;
import net.minecraft.world.World;
import net.minecraft.world.WorldSettings;
import net.minecraft.world.WorldType;
import net.minecraft.world.chunk.storage.AnvilSaveConverter;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.atom.AtomServerWatchDog;
import org.atom.Metrics;
import org.bukkit.craftbukkit.LoggerOutputStream;
import org.bukkit.craftbukkit.SpigotTimings; // Spigot
import org.bukkit.craftbukkit.util.Waitable;
import org.bukkit.event.server.RemoteServerCommandEvent;
import org.bukkit.event.server.ServerCommandEvent;

@SideOnly(Side.SERVER)
public class DedicatedServer extends MinecraftServer implements IServer {
    private static final Logger LOGGER = LogManager.getLogger();
    private static final Pattern RESOURCE_PACK_SHA1_PATTERN = Pattern.compile("^[a-fA-F0-9]{40}$");
    public final List<PendingCommand> pendingCommandList = Collections.<PendingCommand>synchronizedList(Lists.newArrayList());
    private RConThreadQuery rconQueryThread;
    public final RConConsoleSource rconConsoleSource = new RConConsoleSource(this);
    private RConThreadMain rconThread;
    public PropertyManager settings;
    private ServerEula eula;
    private boolean canSpawnStructures;
    private GameType gameType;
    private boolean guiIsEnabled;
    public static boolean allowPlayerLogins = false;
    private static boolean metricsStarted;

    // CraftBukkit start - Signature changed
    public DedicatedServer(joptsimple.OptionSet options, DataFixer dataFixerIn, YggdrasilAuthenticationService authServiceIn, MinecraftSessionService sessionServiceIn, GameProfileRepository profileRepoIn, PlayerProfileCache profileCacheIn) {
        super(options, Proxy.NO_PROXY, dataFixerIn, authServiceIn, sessionServiceIn, profileRepoIn, profileCacheIn);
        Thread thread = new Thread("Server Infinisleeper") {
            {
                this.setDaemon(true);
                this.start();
            }

            public void run() {
                while (true) {
                    try {
                        Thread.sleep(2147483647L);
                    } catch (InterruptedException var2) {
                        ;
                    }
                }
            }
        };
    }

    public boolean init() throws IOException {
        Thread thread = new Thread("Server console handler") {
            public void run() {
                BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
                if (net.minecraftforge.server.console.TerminalHandler.handleCommands(DedicatedServer.this)) return;
                String s4;


                try {
                    while (!DedicatedServer.this.isServerStopped() && DedicatedServer.this.isServerRunning() && (s4 = bufferedreader.readLine()) != null) {
                        DedicatedServer.this.addPendingCommand(s4, DedicatedServer.this);
                    }
                } catch (IOException ioexception1) {
                    DedicatedServer.LOGGER.error("Exception handling console input", (Throwable) ioexception1);
                }
            }
        };

        // CraftBukkit start - TODO: handle command-line logging arguments
        java.util.logging.Logger global = java.util.logging.Logger.getLogger("");
        global.setUseParentHandlers(false);
        for (java.util.logging.Handler handler : global.getHandlers()) {
            global.removeHandler(handler);
        }
        global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler());

        final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger());
        for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) {
            if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) {
                logger.removeAppender(appender);
            }
        }

        System.setOut(new PrintStream(new LoggerOutputStream(logger, Level.INFO), true));
        System.setErr(new PrintStream(new LoggerOutputStream(logger, Level.WARN), true));
        // CraftBukkit end

        thread.setDaemon(true);
        thread.start();
        LOGGER.info("Starting minecraft server version 1.12.2");

        if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) {
            LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
        }

        net.minecraftforge.fml.common.FMLCommonHandler.instance().onServerStart(this);

        LOGGER.info("Loading properties");
        this.settings = new PropertyManager(this.options); // CraftBukkit - CLI argument support
        this.eula = new ServerEula(new File("eula.txt"));

        if (!this.eula.hasAcceptedEULA()) {
            LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
            this.eula.createEULAFile();
            return false;
        } else {
            if (this.isSinglePlayer()) {
                this.setHostname("127.0.0.1");
            } else {
                this.setOnlineMode(this.settings.getBooleanProperty("online-mode", true));
                this.setPreventProxyConnections(this.settings.getBooleanProperty("prevent-proxy-connections", false));
                this.setHostname(this.settings.getStringProperty("server-ip", ""));
            }

            this.setCanSpawnAnimals(this.settings.getBooleanProperty("spawn-animals", true));
            this.setCanSpawnNPCs(this.settings.getBooleanProperty("spawn-npcs", true));
            this.setAllowPvp(this.settings.getBooleanProperty("pvp", true));
            this.setAllowFlight(this.settings.getBooleanProperty("allow-flight", false));
            this.setResourcePack(this.settings.getStringProperty("resource-pack", ""), this.loadResourcePackSHA());
            this.setMOTD(this.settings.getStringProperty("motd", "A Minecraft Server"));
            this.setForceGamemode(this.settings.getBooleanProperty("force-gamemode", false));
            this.setPlayerIdleTimeout(this.settings.getIntProperty("player-idle-timeout", 0));

            if (this.settings.getIntProperty("difficulty", 1) < 0) {
                this.settings.setProperty("difficulty", Integer.valueOf(0));
            } else if (this.settings.getIntProperty("difficulty", 1) > 3) {
                this.settings.setProperty("difficulty", Integer.valueOf(3));
            }

            this.canSpawnStructures = this.settings.getBooleanProperty("generate-structures", true);
            int i = this.settings.getIntProperty("gamemode", GameType.SURVIVAL.getID());
            this.gameType = WorldSettings.getGameTypeById(i);
            LOGGER.info("Default game type: {}", (Object) this.gameType);
            InetAddress inetaddress = null;

            if (!this.getServerHostname().isEmpty()) {
                inetaddress = InetAddress.getByName(this.getServerHostname());
            }

            if (this.getServerPort() < 0) {
                this.setServerPort(this.settings.getIntProperty("server-port", 25565));
            }
            // Spigot start
            setPlayerList(new DedicatedPlayerList(this));
            org.spigotmc.SpigotConfig.init((File) options.valueOf("spigot-settings"));
            org.spigotmc.SpigotConfig.registerCommands();
            // Spigot end

            LOGGER.info("Generating keypair");
            this.setKeyPair(CryptManager.generateKeyPair());
            LOGGER.info("Starting Minecraft server on {}:{}", this.getServerHostname().isEmpty() ? "*" : this.getServerHostname(), Integer.valueOf(this.getServerPort()));

            try {
                this.getNetworkSystem().addLanEndpoint(inetaddress, this.getServerPort());
            } catch (IOException ioexception) {
                LOGGER.warn("**** FAILED TO BIND TO PORT!");
                LOGGER.warn("The exception was: {}", (Object) ioexception.toString());
                LOGGER.warn("Perhaps a server is already running on that port?");
                return false;
            }

            //  this.setPlayerList(new DedicatedPlayerList(this)); // Spigot - moved up
            server.loadPlugins();
            server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP);

            if (!this.isServerInOnlineMode()) {
                LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
                LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
                LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
                LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
            }

            if (this.convertFiles()) {
                this.getPlayerProfileCache().save();
            }

            if (!PreYggdrasilConverter.tryConvert(this.settings)) {
                return false;
            } else {
                net.minecraftforge.fml.common.FMLCommonHandler.instance().onServerStarted();
                this.anvilConverterForAnvilFile = new AnvilSaveConverter(server.getWorldContainer(), this.dataFixer); // CraftBukkit - moved from MinecraftServer constructor
//                this.setPlayerList(new DedicatedPlayerList(this));
                long j = System.nanoTime();

                if (this.getFolderName() == null) {
                    this.setFolderName(this.settings.getStringProperty("level-name", "world"));
                }

                String s = this.settings.getStringProperty("level-seed", "");
                String s1 = this.settings.getStringProperty("level-type", "DEFAULT");
                String s2 = this.settings.getStringProperty("generator-settings", "");
                long k = (new Random()).nextLong();

                if (!s.isEmpty()) {
                    try {
                        long l = Long.parseLong(s);

                        if (l != 0L) {
                            k = l;
                        }
                    } catch (NumberFormatException var16) {
                        k = (long) s.hashCode();
                    }
                }

                WorldType worldtype = WorldType.parseWorldType(s1);

                if (worldtype == null) {
                    worldtype = WorldType.DEFAULT;
                }

                this.isCommandBlockEnabled();
                this.getOpPermissionLevel();
                this.isSnooperEnabled();
                this.getNetworkCompressionThreshold();
                this.setBuildLimit(this.settings.getIntProperty("max-build-height", 256));
                this.setBuildLimit((this.getBuildLimit() + 8) / 16 * 16);
                this.setBuildLimit(MathHelper.clamp(this.getBuildLimit(), 64, 256));
                this.settings.setProperty("max-build-height", Integer.valueOf(this.getBuildLimit()));
                TileEntitySkull.setProfileCache(this.getPlayerProfileCache());
                TileEntitySkull.setSessionService(this.getMinecraftSessionService());
                PlayerProfileCache.setOnlineMode(this.isServerInOnlineMode());
                if (!net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerAboutToStart(this))
                    return false;
                LOGGER.info("Preparing level \"{}\"", (Object) this.getFolderName());
                this.loadAllWorlds(this.getFolderName(), this.getFolderName(), k, worldtype, s2);
                long i1 = System.nanoTime() - j;
                String s3 = String.format("%.3fs", (double) i1 / 1.0E9D);
                LOGGER.info("Done ({})! For help, type \"help\" or \"?\"", (Object) s3);
                if (!metricsStarted) {
                    Metrics.PaperMetrics.startMetrics();
                    metricsStarted = true;
                }
                this.currentTime = getCurrentTimeMillis();

                if (this.settings.hasProperty("announce-player-achievements")) {
                    this.worldServerList.get(0).getGameRules().setOrCreateGameRule("announceAdvancements", this.settings.getBooleanProperty("announce-player-achievements", true) ? "true" : "false");
                    this.settings.removeProperty("announce-player-achievements");
                    this.settings.saveProperties();
                }

                if (this.settings.getBooleanProperty("enable-query", false)) {
                    LOGGER.info("Starting GS4 status listener");
                    this.rconQueryThread = new RConThreadQuery(this);
                    this.rconQueryThread.startThread();
                }

                if (this.settings.getBooleanProperty("enable-rcon", false)) {
                    LOGGER.info("Starting remote control listener");
                    this.rconThread = new RConThreadMain(this);
                    this.rconThread.startThread();
                    this.remoteConsole = new org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender(this.rconConsoleSource);
                }

                if (this.server.getBukkitSpawnRadius() > -1) {
                    DedicatedServer.LOGGER.info("'settings.spawn-radius' in bukkit.yml has been moved to 'spawn-protection' in server.properties. I will move your config for you.");
                    this.settings.serverProperties.remove("spawn-protection");
                    this.settings.getIntProperty("spawn-protection", this.server.getBukkitSpawnRadius());
                    this.server.removeBukkitSpawnRadius();
                    this.settings.saveProperties();
                }

                if (getMaxTickTime() > -1) {
                    try {
                        AtomServerWatchDog.startWatchDog(this);
                    } catch (IllegalAccessException e) {
                        logger.error("init server watch dog failed..");
                    }

                }

                Items.AIR.getSubItems(CreativeTabs.SEARCH, NonNullList.create());
                // <3 you Grum for this, saves us ~30 patch files! --^
                return net.minecraftforge.fml.common.FMLCommonHandler.instance().handleServerStarting(this);
            }
        }
    }

    public String loadResourcePackSHA() {
        if (this.settings.hasProperty("resource-pack-hash")) {
            if (this.settings.hasProperty("resource-pack-sha1")) {
                LOGGER.warn("resource-pack-hash is deprecated and found along side resource-pack-sha1. resource-pack-hash will be ignored.");
            } else {
                LOGGER.warn("resource-pack-hash is deprecated. Please use resource-pack-sha1 instead.");
                this.settings.getStringProperty("resource-pack-sha1", this.settings.getStringProperty("resource-pack-hash", ""));
                this.settings.removeProperty("resource-pack-hash");
            }
        }

        String s = this.settings.getStringProperty("resource-pack-sha1", "");

        if (!s.isEmpty() && !RESOURCE_PACK_SHA1_PATTERN.matcher(s).matches()) {
            LOGGER.warn("Invalid sha1 for ressource-pack-sha1");
        }

        if (!this.settings.getStringProperty("resource-pack", "").isEmpty() && s.isEmpty()) {
            LOGGER.warn("You specified a resource pack without providing a sha1 hash. Pack will be updated on the client only if you change the name of the pack.");
        }

        return s;
    }

    public void setGameType(GameType gameMode) {
        super.setGameType(gameMode);
        this.gameType = gameMode;
    }

    public boolean canStructuresSpawn() {
        return this.canSpawnStructures;
    }

    public GameType getGameType() {
        return this.gameType;
    }

    public EnumDifficulty getDifficulty() {
        return EnumDifficulty.getDifficultyEnum(this.settings.getIntProperty("difficulty", EnumDifficulty.NORMAL.getDifficultyId()));
    }

    public boolean isHardcore() {
        return this.settings.getBooleanProperty("hardcore", false);
    }

    public CrashReport addServerInfoToCrashReport(CrashReport report) {
        report = super.addServerInfoToCrashReport(report);
        report.getCategory().addDetail("Is Modded", new ICrashReportDetail<String>() {
            public String call() throws Exception {
                String s = DedicatedServer.this.getServerModName();
                return !"vanilla".equals(s) ? "Definitely; Server brand changed to '" + s + "'" : "Unknown (can't tell)";
            }
        });
        report.getCategory().addDetail("Type", new ICrashReportDetail<String>() {
            public String call() throws Exception {
                return "Dedicated Server (map_server.txt)";
            }
        });
        return report;
    }

    public void systemExitNow() {
        System.exit(0);
    }

    public void updateTimeLightAndEntities() {
        super.updateTimeLightAndEntities();
        this.executePendingCommands();
    }

    public boolean getAllowNether() {
        return this.settings.getBooleanProperty("allow-nether", true);
    }

    public boolean allowSpawnMonsters() {
        return this.settings.getBooleanProperty("spawn-monsters", true);
    }

    public void addServerStatsToSnooper(Snooper playerSnooper) {
        playerSnooper.addClientStat("whitelist_enabled", Boolean.valueOf(this.getPlayerList().isWhiteListEnabled()));
        playerSnooper.addClientStat("whitelist_count", Integer.valueOf(this.getPlayerList().getWhitelistedPlayerNames().length));
        super.addServerStatsToSnooper(playerSnooper);
    }

    public boolean isSnooperEnabled() {
        return this.settings.getBooleanProperty("snooper-enabled", true);
    }

    public void addPendingCommand(String input, ICommandSender sender) {
        this.pendingCommandList.add(new PendingCommand(input, sender));
    }

    public void executePendingCommands() {
        SpigotTimings.serverCommandTimer.startTiming(); // Spigot
        while (!this.pendingCommandList.isEmpty()) {
            PendingCommand pendingcommand = this.pendingCommandList.remove(0);
            // CraftBukkit start - ServerCommand for preprocessing
            ServerCommandEvent event = new ServerCommandEvent(console, pendingcommand.command);
            server.getPluginManager().callEvent(event);
            if (event.isCancelled()) continue;
            pendingcommand = new PendingCommand(event.getCommand(), pendingcommand.sender);

            // this.getCommandManager().executeCommand(pendingcommand.sender, pendingcommand.command); // Called in dispatchServerCommand
            server.dispatchServerCommand(console, pendingcommand);
            // CraftBukkit end
        }
        SpigotTimings.serverCommandTimer.stopTiming(); // Spigot
    }

    public boolean isDedicatedServer() {
        return true;
    }

    public boolean shouldUseNativeTransport() {
        return this.settings.getBooleanProperty("use-native-transport", true);
    }

    public DedicatedPlayerList getPlayerList() {
        return (DedicatedPlayerList) super.getPlayerList();
    }

    public int getIntProperty(String key, int defaultValue) {
        return this.settings.getIntProperty(key, defaultValue);
    }

    public String getStringProperty(String key, String defaultValue) {
        return this.settings.getStringProperty(key, defaultValue);
    }

    public boolean getBooleanProperty(String key, boolean defaultValue) {
        return this.settings.getBooleanProperty(key, defaultValue);
    }

    public void setProperty(String key, Object value) {
        this.settings.setProperty(key, value);
    }

    public void saveProperties() {
        this.settings.saveProperties();
    }

    public String getSettingsFilename() {
        File file1 = this.settings.getPropertiesFile();
        return file1 != null ? file1.getAbsolutePath() : "No settings file";
    }

    public String getHostname() {
        return this.getServerHostname();
    }

    public int getPort() {
        return this.getServerPort();
    }

    public String getMotd() {
        return this.getMOTD();
    }

    public void setGuiEnabled() {
        MinecraftServerGui.createServerGui(this);
        this.guiIsEnabled = true;
    }

    public boolean getGuiEnabled() {
        return this.guiIsEnabled;
    }

    public String shareToLAN(GameType type, boolean allowCheats) {
        return "";
    }

    public boolean isCommandBlockEnabled() {
        return this.settings.getBooleanProperty("enable-command-block", false);
    }

    public int getSpawnProtectionSize() {
        return this.settings.getIntProperty("spawn-protection", super.getSpawnProtectionSize());
    }

    public boolean isBlockProtected(World worldIn, BlockPos pos, EntityPlayer playerIn) {
        if (worldIn.provider.getDimension() != 0) {
            return false;
        } else if (this.getPlayerList().getOppedPlayers().isEmpty()) {
            return false;
        } else if (this.getPlayerList().canSendCommands(playerIn.getGameProfile())) {
            return false;
        } else if (this.getSpawnProtectionSize() <= 0) {
            return false;
        } else {
            BlockPos blockpos = worldIn.getSpawnPoint();
            int i = MathHelper.abs(pos.getX() - blockpos.getX());
            int j = MathHelper.abs(pos.getZ() - blockpos.getZ());
            int k = Math.max(i, j);
            return k <= this.getSpawnProtectionSize();
        }
    }

    public int getOpPermissionLevel() {
        return this.settings.getIntProperty("op-permission-level", 4);
    }

    public void setPlayerIdleTimeout(int idleTimeout) {
        super.setPlayerIdleTimeout(idleTimeout);
        this.settings.setProperty("player-idle-timeout", Integer.valueOf(idleTimeout));
        this.saveProperties();
    }

    public boolean shouldBroadcastRconToOps() {
        return this.settings.getBooleanProperty("broadcast-rcon-to-ops", true);
    }

    public boolean shouldBroadcastConsoleToOps() {
        return this.settings.getBooleanProperty("broadcast-console-to-ops", true);
    }

    public int getMaxWorldSize() {
        int i = this.settings.getIntProperty("max-world-size", super.getMaxWorldSize());

        if (i < 1) {
            i = 1;
        } else if (i > super.getMaxWorldSize()) {
            i = super.getMaxWorldSize();
        }

        return i;
    }

    public int getNetworkCompressionThreshold() {
        return this.settings.getIntProperty("network-compression-threshold", super.getNetworkCompressionThreshold());
    }

    //Forge: Enable formated text for colors in console.
    @Override
    public void sendMessage(net.minecraft.util.text.ITextComponent message) {
        LOGGER.info(message.getFormattedText());
    }

    protected boolean convertFiles() throws IOException {
        boolean flag = false;

        for (int i = 0; !flag && i <= 2; ++i) {
            if (i > 0) {
                LOGGER.warn("Encountered a problem while converting the user banlist, retrying in a few seconds");
                this.sleepFiveSeconds();
            }

            flag = PreYggdrasilConverter.convertUserBanlist(this);
        }

        boolean flag1 = false;

        for (int j = 0; !flag1 && j <= 2; ++j) {
            if (j > 0) {
                LOGGER.warn("Encountered a problem while converting the ip banlist, retrying in a few seconds");
                this.sleepFiveSeconds();
            }

            flag1 = PreYggdrasilConverter.convertIpBanlist(this);
        }

        boolean flag2 = false;

        for (int k = 0; !flag2 && k <= 2; ++k) {
            if (k > 0) {
                LOGGER.warn("Encountered a problem while converting the op list, retrying in a few seconds");
                this.sleepFiveSeconds();
            }

            flag2 = PreYggdrasilConverter.convertOplist(this);
        }

        boolean flag3 = false;

        for (int l = 0; !flag3 && l <= 2; ++l) {
            if (l > 0) {
                LOGGER.warn("Encountered a problem while converting the whitelist, retrying in a few seconds");
                this.sleepFiveSeconds();
            }

            flag3 = PreYggdrasilConverter.convertWhitelist(this);
        }

        boolean flag4 = false;

        for (int i1 = 0; !flag4 && i1 <= 2; ++i1) {
            if (i1 > 0) {
                LOGGER.warn("Encountered a problem while converting the player save files, retrying in a few seconds");
                this.sleepFiveSeconds();
            }

            flag4 = PreYggdrasilConverter.convertSaveFiles(this, this.settings);
        }

        return flag || flag1 || flag2 || flag3 || flag4;
    }

    private void sleepFiveSeconds() {
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException var2) {
            ;
        }
    }

    public long getMaxTickTime() {
        return this.settings.getLongProperty("max-tick-time", TimeUnit.MINUTES.toMillis(1L));
    }

    public String getPlugins() {
        // CraftBukkit start - Whole method
        StringBuilder result = new StringBuilder();
        org.bukkit.plugin.Plugin[] plugins = server.getPluginManager().getPlugins();

        result.append(server.getName());
        result.append(" on Bukkit ");
        result.append(server.getBukkitVersion());

        if (plugins.length > 0 && server.getQueryPlugins()) {
            result.append(": ");

            for (int i = 0; i < plugins.length; i++) {
                if (i > 0) {
                    result.append("; ");
                }

                result.append(plugins[i].getDescription().getName());
                result.append(" ");
                result.append(plugins[i].getDescription().getVersion().replaceAll(";", ","));
            }
        }

        return result.toString();
        // CraftBukkit end
    }

    // CraftBukkit start - fire RemoteServerCommandEvent
    public String handleRConCommand(final String command) {
        Waitable<String> waitable = new Waitable<String>() {
            @Override
            protected String evaluate() {
                rconConsoleSource.resetLog();
                // Event changes start
                RemoteServerCommandEvent event = new RemoteServerCommandEvent(remoteConsole, command);
                server.getPluginManager().callEvent(event);
                if (event.isCancelled()) {
                    return "";
                }
                // Event change end
                PendingCommand serverCommand = new PendingCommand(event.getCommand(), rconConsoleSource);
                server.dispatchServerCommand(remoteConsole, serverCommand);
                return rconConsoleSource.getLogContents();
            }
        };
        processQueue.add(waitable);
        try {
            return waitable.get();
        } catch (java.util.concurrent.ExecutionException e) {
            throw new RuntimeException("Exception processing rcon command " + command, e.getCause());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // Maintain interrupted state
            throw new RuntimeException("Interrupted processing rcon command " + command, e);
        }
    }
    // CraftBukkit end

    // TODO: Check if it's useless
    @Override
    public PropertyManager getPropertyManager() {
        return this.settings;
    }

    public static Logger getLOGGER() {
        return LOGGER;
    }
}